﻿//-------------------------------------------------------------
// Copyright © Berico Technologies, LLC. All Rights Reserved
//
// This source is subject to the Microsoft Public License. Please
// visit http://www.microsoft.com/opensource/licenses.mspx#Ms-PL
// for more information.
//
// SnagL™ is a trademark of Berico Technologies.
//-------------------------------------------------------------

using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using Berico.SnagL.Infrastructure.Events;
using Berico.SnagL.Infrastructure.Graph.Events;
using Berico.Windows.Controls;

namespace Berico.SnagL.Infrastructure.Graph
{
    /// <summary>
    /// Responsible for managing the ContextMenu control's
    /// used in SnagL
    /// </summary>
    public class ContextMenuManager
    {
        #region Fields

        /// <summary>
        /// Stores the singleton instance of this class
        /// </summary>
        private static ContextMenuManager instance;

        /// <summary>
        /// Used to synchronize threads
        /// </summary>
        private static object syncRoot = new object();

        /// <summary>
        /// Stores a collection of <see cref="MenuItem"/> objects on the graph
        /// </summary>
        private ObservableCollection<MenuItem> graphMenuItems = new ObservableCollection<MenuItem>();

        /// <summary>
        /// Stores a collection of <see cref="MenuItem"/> objects on nodes
        /// </summary>
        private ObservableCollection<MenuItem> nodeMenuItems = new ObservableCollection<MenuItem>();

        /// <summary>
        /// Stores a collection of <see cref="MenuItem"/> objects for edges
        /// </summary>
        private ObservableCollection<MenuItem> edgeMenuItems = new ObservableCollection<MenuItem>();

        /// <summary>
        /// Stores a reference to the graph context menu
        /// </summary>
        private ContextMenu graphContextMenu;

        /// <summary>
        /// Stores a reference to the node context menu
        /// </summary>
        private ContextMenu nodeContextMenu;

        /// <summary>
        /// Stores a reference to the edge context menu
        /// </summary>
        private ContextMenu edgeContextMenu;

        #endregion

        #region Constructors

        /// <summary>
        /// Prevents a default instance of the <see cref="ContextMenuManager"/> class from being created
        /// </summary>
        private ContextMenuManager()
        {
            this.TargetNodeVm = null;
        }

        #endregion

        #region Events

        /// <summary>
        /// Indicates the Graph's <see cref="ContextMenu"/> is opening
        /// </summary>
        public event EventHandler<EventArgs> GraphContextMenuOpening;

        /// <summary>
        /// Indicates the Node's <see cref="ContextMenu"/> is opening
        /// </summary>
        public event EventHandler<ContextMenuEventArgs> NodeContextMenuOpening;

        /// <summary>
        /// Indicates the Node's <see cref="ContextMenu"/> has closed
        /// </summary>
        public event EventHandler<EventArgs> NodeContextMenuClosed;

        /// <summary>
        /// Indicates the Edge's <see cref="ContextMenu"/> is opening
        /// </summary>
        public event EventHandler<ContextMenuEventArgs> EdgeContextMenuOpening;

        /// <summary>
        /// Indicates the Edge's <see cref="ContextMenu"/> has closed
        /// </summary>
        public event EventHandler<EventArgs> EdgeContextMenuClosed;

        #endregion

        #region Properties

        /// <summary>
        /// Gets the instance of the ContextMenuManager class
        /// </summary>
        public static ContextMenuManager Instance
        {
            get
            {
                if (instance == null)
                {
                    lock (syncRoot)
                    {
                        if (instance == null)
                        {
                            instance = new ContextMenuManager();
                            instance.Initialize();
                        }
                    }
                }

                return instance;
            }
        }

        /// <summary>
        /// Gets an array of delegates containing context menu callbacks
        /// </summary>
        public Delegate[] ContextMenuOpeningInvocationList
        {
            get
            {
                return GraphContextMenuOpening.GetInvocationList();
            }
        }

        /// <summary>
        /// Gets a reference to the <see cref="NodeViewModelBase"/> for which the context menu references
        /// </summary>
        public NodeViewModelBase TargetNodeVm
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets a reference to the <see cref="IEdgeViewModel"/> for which the context menu references
        /// </summary>
        public IEdgeViewModel TargetEdgeVm
        {
            get;
            private set;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Adds the provided item to the Graph's ContextMenu
        /// </summary>
        /// <param name="newMenuItem">The menu item to add to the ContextMenu</param>
        public void AddGraphMenuItem(MenuItem newMenuItem)
        {
            MenuItem existingMenuItem = this.graphMenuItems.FirstOrDefault(menuitems => menuitems.Header == newMenuItem.Header);

            if (existingMenuItem != null)
            {
                graphMenuItems.Remove(existingMenuItem);
            }

            graphMenuItems.Add(newMenuItem);
        }

        /// <summary>
        /// Adds the provided item to the Node's ContextMenu
        /// </summary>
        /// <param name="newMenuItem">The menu item to add to the ContextMenu</param>
        public void AddNodeMenuItem(MenuItem newMenuItem)
        {
            MenuItem existingMenuItem = nodeMenuItems.FirstOrDefault(menuitems => menuitems.Header == newMenuItem.Header);

            if (existingMenuItem != null)
            {
                nodeMenuItems.Remove(existingMenuItem);
            }

            nodeMenuItems.Add(newMenuItem);
        }

        /// <summary>
        /// Adds the provided item to the Edge's ContextMenu
        /// </summary>
        /// <param name="newMenuItem">The menu item to add to the ContextMenu</param>
        public void AddEdgeMenuItem(MenuItem newMenuItem)
        {
            MenuItem existingMenuItem = edgeMenuItems.FirstOrDefault(menuitems => menuitems.Header == newMenuItem.Header);

            if (existingMenuItem != null)
            {
                edgeMenuItems.Remove(existingMenuItem);
            }

            edgeMenuItems.Add(newMenuItem);
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Handles the MouseRightButtonDown event for the Graph
        /// </summary>
        /// <param name="args">Arguments for the event</param>
        public void GraphMouseRightButtonDownEventHandler(MouseButtonEventArgs args)
        {
            // Ensure that the context menu has not already been initialized
            if (graphContextMenu == null)
            {
                graphContextMenu = new ContextMenu();
                graphContextMenu.ItemsSource = graphMenuItems;
                graphContextMenu.Opening += new RoutedEventHandler(GraphContextMenuOpeningHandler);
            }

            // Open the Graph's ContextMenu
            graphContextMenu.OpenPopup(args.GetPosition(null));
        }

        /// <summary>
        /// Handles the MouseRightButtonDown event for the Node
        /// </summary>
        /// <param name="args">Arguments for the event</param>
        public void NodeMouseRightButtonDownEventHandler(NodeViewModelMouseEventArgs<MouseButtonEventArgs> args)
        {
            // Ensure that the context menu has not already been initialized
            if (nodeContextMenu == null)
            {
                nodeContextMenu = new ContextMenu();
                nodeContextMenu.ItemsSource = nodeMenuItems;
                nodeContextMenu.Opening += new RoutedEventHandler(NodeContextMenuOpeningHandler);
                nodeContextMenu.Closed += new RoutedEventHandler(NodeContextMenuClosedHandler);
            }

            // Save the NodeViewModel that the right click was on
            this.TargetNodeVm = args.NodeViewModel;

            // Open the Node's ContextMenu
            nodeContextMenu.OpenPopup(args.MouseArgs.GetPosition(null));
        }

        /// <summary>
        /// Handles the <see cref="EdgeMouseRightButtonDownEventHandler"/>event for the Edge
        /// </summary>
        /// <param name="args">Arguments for the event</param>
        public void EdgeMouseRightButtonDownEventHandler(EdgeViewModelMouseEventArgs<MouseButtonEventArgs> args)
        {
            // Ensure that the context menu has not already been initialized
            if (edgeContextMenu == null)
            {
                edgeContextMenu = new ContextMenu();
                edgeContextMenu.ItemsSource = edgeMenuItems;
                edgeContextMenu.Opening += new RoutedEventHandler(EdgeContextMenuOpeningHandler);
                edgeContextMenu.Closed += new RoutedEventHandler(EdgeContextMenuClosedHandler);
            }

            // Save the EdgeViewModel that the right click was on
            this.TargetEdgeVm = args.EdgeViewModel;

            // Open the Node's ContextMenu
            edgeContextMenu.OpenPopup(args.MouseArgs.GetPosition(null));
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Fires the GraphContextMenuOpening event
        /// </summary>
        /// <param name="e">The arguments for the event</param>
        protected virtual void OnGraphContextMenuOpening(EventArgs e)
        {
            if (GraphContextMenuOpening != null)
            {
                GraphContextMenuOpening(this, e);
            }
        }

        /// <summary>
        /// Fires the NodeContextMenuOpening event
        /// </summary>
        /// <param name="e">The arguments for the event</param>
        protected virtual void OnNodeContextMenuOpening(ContextMenuEventArgs e)
        {
            if (this.NodeContextMenuOpening != null)
            {
                this.NodeContextMenuOpening(this, e);
            }
        }

        /// <summary>
        /// Fires the NodeContextMenuClosed event
        /// </summary>
        /// <param name="e">The arguments for the event</param>
        protected virtual void OnNodeContextMenuClosed(EventArgs e)
        {
            if (NodeContextMenuClosed != null)
            {
                NodeContextMenuClosed(this, e);
            }
        }

        /// <summary>
        /// Fires the EdgeContextMenuOpening event
        /// </summary>
        /// <param name="e">The arguments for the event</param>
        protected virtual void OnEdgeContextMenuOpening(ContextMenuEventArgs e)
        {
            if (this.EdgeContextMenuOpening != null)
            {
                this.EdgeContextMenuOpening(this, e);
            }
        }

        /// <summary>
        /// Fires the EdgeContextMenuClosed event
        /// </summary>
        /// <param name="e">The arguments for the event</param>
        protected virtual void OnEdgeContextMenuClosed(EventArgs e)
        {
            if (EdgeContextMenuClosed != null)
            {
                EdgeContextMenuClosed(this, e);
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Initializes the ContextMenuManager class
        /// </summary>
        private void Initialize()
        {
            SnaglEventAggregator.DefaultInstance.GetEvent<GraphMouseRightButtonDownEvent>().Subscribe(GraphMouseRightButtonDownEventHandler, false);
            SnaglEventAggregator.DefaultInstance.GetEvent<NodeMouseRightButtonDownEvent>().Subscribe(NodeMouseRightButtonDownEventHandler, false);
            SnaglEventAggregator.DefaultInstance.GetEvent<EdgeMouseRightButtonDownEvent>().Subscribe(EdgeMouseRightButtonDownEventHandler, false);

            graphMenuItems = new ObservableCollection<MenuItem>();
            nodeMenuItems = new ObservableCollection<MenuItem>();
            edgeMenuItems = new ObservableCollection<MenuItem>();
        }

        /// <summary>
        /// Handles the Opening event fired by the ContextMenu control
        /// </summary>
        /// <param name="sender">The ContextMenu that fired the event</param>
        /// <param name="e">Arguments for the event</param>
        private void GraphContextMenuOpeningHandler(object sender, System.Windows.RoutedEventArgs e)
        {
            OnGraphContextMenuOpening(EventArgs.Empty);
        }

        /// <summary>
        /// Handles the Opening event fired by the ContextMenu control
        /// </summary>
        /// <param name="sender">The ContextMenu that fired the event</param>
        /// <param name="e">Arguments for the event</param>
        private void NodeContextMenuOpeningHandler(object sender, System.Windows.RoutedEventArgs e)
        {
            this.OnNodeContextMenuOpening(new ContextMenuEventArgs(this.TargetNodeVm));
        }

        /// <summary>
        /// Handles the Closed event fired by the ContextMenu control
        /// </summary>
        /// <param name="sender">The ContextMenu that fired the event</param>
        /// <param name="e">Arguments for the event</param>
        private void NodeContextMenuClosedHandler(object sender, System.Windows.RoutedEventArgs e)
        {
            OnNodeContextMenuClosed(EventArgs.Empty);
        }

        /// <summary>
        /// Handles the Opening event fired by the ContextMenu control
        /// </summary>
        /// <param name="sender">The ContextMenu that fired the event</param>
        /// <param name="e">Arguments for the event</param>
        private void EdgeContextMenuOpeningHandler(object sender, System.Windows.RoutedEventArgs e)
        {
            OnEdgeContextMenuOpening(new ContextMenuEventArgs(this.TargetNodeVm));
        }

        /// <summary>
        /// Handles the Closed event fired by the ContextMenu control
        /// </summary>
        /// <param name="sender">The ContextMenu that fired the event</param>
        /// <param name="e">Arguments for the event</param>
        private void EdgeContextMenuClosedHandler(object sender, System.Windows.RoutedEventArgs e)
        {
            OnEdgeContextMenuClosed(EventArgs.Empty);
        }

        #endregion
    }
}
